"use strict";

const fs = require("fs");
const fse = require("fs-extra");
const path = require("path");
const FError = require("../util/FError");
const { models: FModel, redis: RedisClient } = require("../connect");
const { Driver, LoadLog, InstallLog, Strategy } = FModel;
const { FEnum } = require("../util");
const config = require("../config/config");
const crypto = require("crypto");
const _ = require("lodash");

exports.mergeDriver = async (reqBody, uploadPath) => {
  const { total, fname, fsize } = reqBody;
  const fdirPath = path.join(uploadPath, `${fname}_dir`);
  let { files, resNo } = await getDirSize(fdirPath, total, fsize);
  if (!resNo) return { resNo: false };
  /**合成文件 */
  let contents = [];
  let fileSize = 0;
  files = normalOrder(files);
  files.forEach(file => {
    let fileDir = path.join(fdirPath, file);
    let stat = fs.statSync(fileDir);
    if (!stat) throw FError.InvalidFileStat("invalid file stat", 403);
    if (stat.isFile()) {
      contents.push(fs.readFileSync(fileDir));
      fileSize += stat.size;
    }
  });
  if (fs.existsSync(`${uploadPath}/${fname}`)) {
    fs.unlinkSync(`${uploadPath}/${fname}`);
  }
  contents.forEach(content => {
    fs.appendFile(`${uploadPath}/${fname}`, content, err => { });
  });
  fse.emptyDirSync(fdirPath); /**删除文件夹中的所有文件 */
  fs.rmdirSync(fdirPath); /**删除文件夹 */
  return { filePath: `${uploadPath}/${fname}`, fname, fileSize, resNo: true };
};

function normalOrder(contents) {
  if (!contents || contents.length === 0) return [];
  /**将文件顺序化，不然按照字符串顺序 10 < 2 */
  let orders = contents.map(content => {
    let dots = content.split(".");
    let len = dots.length;
    return +dots[len - 1];
  });
  orders = _.sortBy(orders);
  const prestr = contents[0].slice(0, contents[0].length - 1);
  const lastList = [];
  orders.forEach(order => {
    lastList.push(prestr + order);
  });
  return lastList;
}

async function getDirSize(fdirPath, total, fsize) {
  /**计算fdirPath中的文件数量 */
  const files = fs.readdirSync(fdirPath);
  if (total !== files.length) return { resNo: false };
  /**读取文件大小，计算文件之和是否等于fsize，已确认是否上传完成 */
  let sizeTotal = 0;
  for (let i = 0; i < total; i++) {
    let fstat = fs.statSync(fdirPath + "/" + files[i]);
    if (fstat && fstat.size) sizeTotal += fstat.size;
  }
  if (sizeTotal !== fsize) return { resNo: false };
  return { files, resNo: true };
}

/**
 * 获取最终版本
 * @function acquireLastVer
 * @param {Object} upgPack
 * @returns {*}
 */
exports.acquireLastVer = async upgPack => {
  const { driverType, cpuBits, osType } = upgPack;
  const drivers = await Driver.find({
    isNewest: true,
    driverType,
    $or: [{ cpuBits }, { cpuBits: FEnum.CpuBits.general }],
    osType
  }).skip(0).limit(1).sort({ version: -1 });
  return drivers && drivers[0];
};

/**
 * 获取下载链接
 * @function upgPack
 * @param {Object} upgPack
 * @return {*}
 */
exports.acquireDownload = async upgPack => {
  try {
    const key = FEnum.RedisKey.loadQueue;
    const value = genUpgPackValue(upgPack);
    await RedisClient.multi()
      .sadd(key, value)
      .pexpire(key, FEnum.MaxLimit.LoaderExipreTime)
      .exec();
    return await exports.acquireLastVer(upgPack);
  } catch (err) {
    throw FError.ExistInQueue("busy in queue", 403);
  }
};

/**
 * 生成下载队列中的下载记录
 * @function genUpgPackValue
 * @param {Object} body
 * @param {String} body.hostName pc主机名
 * @param {String} body.userName pc用户名
 * @param {String} body.version  下载版本
 * @param {String} body.osVer    当前版本
 * @param {Number} body.driverType 驱动版本
 * @param {Number} body.cpuBits  驱动位数
 * @param {Number} body.osType  系统类型
 * @returns {*}
 */
function genUpgPackValue(body) {
  let str = "";
  body = body || {};
  Object.keys({
    hostName: body.hostName,
    userName: body.userName,
    version: body.version,
    osVer: body.osVer,
    driverType: body.driverType,
    cpuBits: body.cpuBits,
    osType: body.osType
  }).forEach(key => {
    str += `${key}:${body[`${key}`]}`;
  });
  return str;
}

/**
 * 存储下载日志
 * @function saveDownRes
 * @params {Object} downLog
 * @returns {*}
 */
exports.saveDownRes = async downLog => {
  const key = FEnum.RedisKey.loadQueue;
  const value = genUpgPackValue(downLog);
  /**删除队列中此人的下载记录 */
  await RedisClient.srem(key, value);
  return await new LoadLog(downLog).save();
};

/**
 * 保存安装日志
 * @function saveInstallRes
 * @params {Object} file
 * @params {Object} reqBody
 * @returns {*}
 */
exports.saveInstallRes = async (file, reqBody) => {
  let httpPath = "";
  const { host, port } = config.domain;
  const { path: filepath, name } = file || {};
  const { result } = reqBody;
  if (!result) {
    const filename = `${name}.${new Date().getTime()}`;
    const reader = fs.createReadStream(filepath);
    const upStream = fs.createWriteStream(
      path.join(__dirname, `../hubs/logs/${filename}`)
    );
    reader.pipe(upStream);
    httpPath = `http://${host}:${port}/logs/${filename}`;
  }
  return await new InstallLog({ ...reqBody, httpPath }).save();
};

/**整理查询条件
 * @function setDriverCondition
 * @param {Object} logBody
 * @param {boolean} flag true为查驱动,false为查日志
 * @returns {*}
 */
async function setDriverCondition(logBody, flag) {
  const condition = {};
  logBody = logBody || {};
  const { success, failure } = FEnum.InstallResult;
  if (logBody.startTime && logBody.endTime) {
    const { startTime, endTime } = logBody;
    condition.$and = [
      { createTime: { $lte: new Date(endTime) } },
      { createTime: { $gte: new Date(startTime) } }
    ];
  }
  if (logBody.userName && !flag)
    condition.userName = new RegExp(logBody.userName);
  if (logBody.hostName && !flag)
    condition.hostName = new RegExp(logBody.hostName);
  if (Object.values(FEnum.DriverType).includes(logBody.driverType))
    condition.driverType = logBody.driverType;
  if (Object.values(FEnum.OsType).includes(logBody.osType))
    condition.osType = logBody.osType;
  if (Object.values([success, failure]).includes(logBody.result))
    condition.result = logBody.result;
  if (Object.values([true, false]).includes(logBody.isNewest))
    condition.isNewest = logBody.isNewest;
  if (logBody.oldVer) condition.oldVer = logBody.oldVer;
  if (logBody.newVer) condition.newVer = logBody.newVer;
  return condition;
}

/**
 * 根据条件查找安装日志
 * @function searchInstallLogs
 * @param {Object} logBody
 * @returns {*}
 */
exports.searchInstallLogs = async logBody => {
  const limit = +logBody.count || 20;
  const skip = +logBody.page || 1;
  const sortBy = +logBody.sortBy || FEnum.SortBy.desc;
  const condition = await setDriverCondition(logBody, false);
  return await InstallLog.find(condition)
    .skip((skip - 1) * limit)
    .limit(limit)
    .sort({ createTime: sortBy });
};

/**
 * 根据条件计数日志
 * @function countInstallLogs
 * @param {Object} countInstallLogs
 * @returns {*}
 */
exports.countInstallLogs = async logBody => {
  const condition = await setDriverCondition(logBody, false);
  return await InstallLog.countDocuments(condition);
};

/**
 * 获取驱动列表
 * @function searchDrivers
 * @param {Object} searchDrivers
 * @returns {*}
 */
exports.searchDrivers = async driverBody => {
  const limit = +driverBody.count || 20;
  const skip = +driverBody.page || 1;
  const sortBy = +driverBody.sortBy || FEnum.SortBy.desc;
  const condition = await setDriverCondition(driverBody, true);
  return await Driver.find(condition)
    .skip((skip - 1) * limit)
    .limit(limit)
    .sort({ createTime: sortBy });
};

/**
 * 计数驱动
 * @function countDrivers
 * @param {Object} countDrivers
 * @returns {*}
 */
exports.countDrivers = async driverBody => {
  const condition = await setDriverCondition(driverBody, true);
  return await Driver.countDocuments(condition);
};

/**
 * 删除驱动
 * @function removeDriver
 * @param {String} did 驱动id
 * @returns {*}
 */
exports.removeDriver = async did => {
  const driver = await Driver.findByIdAndRemove(did);
  const { driverType, osType, cpuBits } = driver;
  await checkOriVersion(driverType, osType, cpuBits);
  const { fileName } = driver || {};
  const hus = path.join(__dirname, "../hubs");
  if (fs.existsSync(hus + `/${fileName}`)) {
    fs.unlinkSync(hus + `/${fileName}`);
  }
  return true;
};

/**
 * 
 * @param {*} driverType 
 * @param {*} osType 
 * @param {*} cpuBits 
 */
async function checkOriVersion(driverType, osType, cpuBits) {
  /**如果删掉最新的，检索次新的更新isNewest */
  const lastDriver = await Driver.find({
    driverType,
    osType,
    cpuBits
  })
    .skip(0)
    .limit(1)
    .sort({ version: -1 });
  if (lastDriver.length > 0) {
    await Driver.updateOne(
      { _id: lastDriver[0]._id },
      { $set: { isNewest: true } }
    );
  }
}

/**
 * 设置驱动资料
 * @function drivenSet
 * @param {Objetc} driverSet
 * @param {String} driverSet.fileName 驱动名
 * @param {Number|FEnum.DriverType} driverSet.driverType 驱动类型
 * @param {Number|FEnum.CpuBits} driverSet.cpuBits 系统位数
 * @param {Number|FEnum.OsType} driverSet.osType 系统类型
 * @returns {*}
 */
exports.drivenSet = async driverSet => {
  /**
   * 1.保存数据进入数据库
   * 2.生成md5
   * 3.生成静态链接
   * 4.判断是否是同类型中最新的驱动
   */
  const driven = await new Driver({
    ...driverSet,
    fileName: driverSet.fileName || new Date().getTime()
  }).save();
  const md5sum = await genMd5Sum(driven);
  await Driver.updateOne({ _id: driven._id }, { $set: { md5: md5sum } });
  await genHttpPath(driven);
  await checkNewestDriven(driven);
  return driven;
};

exports.updateDriverSet = async (did, drivenSet) => {
  const oldDrievn = await Driver.findById(did);
  const { driverType, osType, cpuBits } = oldDrievn;
  const driven = await Driver.findByIdAndUpdate(
    did,
    { $set: drivenSet },
    { new: true }
  );
  const md5sum = await genMd5Sum(driven);
  await Driver.updateOne({ _id: driven._id }, { $set: { md5: md5sum } });
  await genHttpPath(driven);
  await checkNewestDriven(driven);
  await checkOriVersion(driverType, osType, cpuBits)
  return driven;
};

/**
 * 更新文档的md5
 * @function genMd5Sum
 * @param {Object} driven
 * @returns {*}
 */
function genMd5Sum(driven) {
  return new Promise(resolve => {
    const { filePath } = driven;
    let md5sum = crypto.createHash("md5");
    let stream = fs.createReadStream(filePath);
    stream.on("data", function (chunk) {
      md5sum.update(chunk);
    });
    stream.on("end", function () {
      let str = md5sum.digest("hex").toUpperCase();
      resolve(str);
    });
  });
}

/**
 * 更新静态链接
 * @function genHttpPath
 * @param {Object} driven
 * @returns {*}
 */
async function genHttpPath(driven) {
  const { _id, filePath } = driven;
  const fpaths = filePath.split("/");
  const flen = fpaths.length;
  const fname = fpaths[flen - 1];
  const { host, port } = config.domain;
  const httpPath = `http://${host}:${port}/${fname}`;
  await Driver.updateOne({ _id }, { $set: { httpPath } });
}

/**
 * 检查更新最新的驱动
 * @function checkNewestDriven
 * @param {Object} driven
 * @returns {*}
 */
async function checkNewestDriven(driven) {
  const { driverType, cpuBits, osType, _id, version: curVer } = driven;
  /**之前的驱动 */
  const driver = await Driver.findOne({
    driverType,
    cpuBits,
    osType,
    isNewest: true
  });
  if (!driver) {
    return await Driver.updateOne({ _id }, { $set: { isNewest: true } });
  } else {
    const { version: preVer, _id: preId } = driver;
    if (curVer >= preVer) {
      await Driver.updateOne({ _id: preId }, { $set: { isNewest: false } });
      await Driver.updateOne({ _id }, { $set: { isNewest: true } });
    }
    return driven;
  }
}

exports.acquireVersions = async () => {
  const oldVers = await InstallLog.aggregate([
    { $group: { _id: "$oldVer", oldVer: { $first: "$oldVer" } } },
    { $sort: { _id: -1 } }
  ]);
  const newVers = await InstallLog.aggregate([
    { $group: { _id: "$newVer", newVer: { $first: "$newVer" } } },
    { $sort: { _id: -1 } }
  ]);
  return {
    oldVers: oldVers.map(o => o.oldVer),
    newVers: newVers.map(n => n.newVer)
  };
};

exports.setStrategy = async strategyBody => {
  await Strategy.deleteMany({});
  return await new Strategy(strategyBody || {}).save();
};

exports.getStrategy = async () => {
  const strategy = await Strategy.findOne();
  return strategy || { timeInterval: 600, method: FEnum.InstallMethod.m0 };
};
